package com.seari.custody.service.Impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.date.*;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.text.escape.Html4Unescape;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.RuntimeUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.poi.word.DocUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.api.client.util.Lists;
import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.Maps;
import com.seari.custody.bean.*;
import com.seari.custody.component.*;
import com.seari.custody.config.CaffeineConfig;
import com.seari.custody.enums.MeetingFileType;
import com.seari.custody.enums.MeetingType;
import com.seari.custody.enums.MessageType;
import com.seari.custody.exception.BIZException;
import com.seari.custody.mapper.MeetingMapper;
import com.seari.custody.service.*;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.core.io.ByteArrayResource;
import org.springframework.core.io.InputStreamSource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.File;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.seari.custody.component.OutlineProjectCalc.createParagraph;

@Slf4j
@Service
@Transactional(rollbackFor = Exception.class)
public class MeetingServiceImpl implements MeetingService {
    private static ThreadLocal<String> COMMON_NAME = new ThreadLocal<>();
    public static final String SYMBOL = "、";
    public static final String DATE_FORMAT_HH_MM = "HH:mm";
    @Value("${oaMeeting.room.list}")
    private String meetingRoomList;
    @Value("${oaMeeting.room.order}")
    private String meetingRoomOrder;
    @Value("${oaMeeting.room.cancel}")
    private String meetingRoomCancel;
    @Value("${outerLink}")
    private String outerLink;
    @Value("${innerLink}")
    private String innerLink;
    @Value("${cmdPath.ffmpeg}")
    private String ffmpegPath;
    @Autowired
    private MinIoUtil minIoUtil;
    @Autowired
    private MeetingMapper meetingMapper;
    @Autowired
    private WeChatMessagePush weChatMessagePush;
    @Autowired
    private MessageService messageService;
    @Autowired
    private UserService userService;
    @Autowired
    private WebserviceClient webserviceClient;
    @Autowired
    private MeetingFileService meetingFileService;
    @Autowired
    private MeetingInviteLink meetingInviteLink;

    @Autowired
    private MeetingUserService meetingUserService;
    @Autowired
    private ProjectService projectService;
    @Autowired
    private MessagePush messagePush;
    @Autowired
    private ProjectManageService projectManageService;

    @Override
    public Meeting selectCzHrtableById(String id, String userNumber) {

        Meeting meeting = meetingMapper.selectCzHrtableById(id);

        CountDownLatch countDownLatch = new CountDownLatch(5);


        try {
            ThreadUtil.execAsync(() -> {
                try {
                    meeting.setOrgUserName(userService.qryNameByUserNumber(meeting.getOrgUserNumber()));
                } catch (Exception e) {
                    log.error(e.getMessage(), e);
                } finally {
                    countDownLatch.countDown();
                }
            });
            ThreadUtil.execAsync(() -> {
                try {
                    meeting.setProjectName(projectService.qryByProjectId(meeting.getProjectId()).getProjectName());
                } catch (Exception e) {
                    log.error(e.getMessage(), e);
                } finally {
                    countDownLatch.countDown();
                }
            });
            ThreadUtil.execAsync(() -> {
                try {
                    meeting.setAckMsgs(qryAttendMeetingUsersStatus(meeting));
                } catch (Exception e) {
                    log.error(e.getMessage(), e);
                } finally {
                    countDownLatch.countDown();
                }
            });
            ThreadUtil.execAsync(() -> {
                try {
                    meeting.setParticipantList(meetingUserService.getMeetingsByMeetingId(id));
                } catch (Exception e) {
                    log.error(e.getMessage(), e);
                } finally {
                    countDownLatch.countDown();
                }
            });

            ThreadUtil.execAsync(() -> {
                try {
//                    meeting.setInviteLink(meetingInviteLink.genInviteLink(meeting, userNumber, InviteLinkType.PC_USR));
                    //链接在会议开始前30分钟出现
                    Date startTime = meeting.getStartTime();
                    Date endTime = meeting.getEndTime();
                    DateTime beforeStartTime = DateUtil.offsetMinute(startTime, -30);
                    boolean in = DateUtil.isIn(new Date(), beforeStartTime, endTime);

                    if (MeetingType.ONLINE.getType().equalsIgnoreCase(meeting.getType()) && in) {
                        meeting.setInviteLink("go");
                    } else {
                        meeting.setInviteLink("");
                    }
                } catch (Exception e) {
                    log.error(e.getMessage(), e);
                } finally {
                    countDownLatch.countDown();
                }
            });

            countDownLatch.await(30, TimeUnit.SECONDS);
        } catch (
                Exception e) {
            throw new BIZException(e);
        }
        meeting.setSite(MeetingType.getDesc(meeting.getType()) + ":" + meeting.getSite());
        return meeting;
    }

    /**
     * 查询参会人员反馈状态
     */
    private String qryAttendMeetingUsersStatus(Meeting meeting) {

        List<MeetingUser> meetingsByMeetingId = meetingUserService.getMeetingsByMeetingId(meeting.getId());
        StringBuilder status1 = new StringBuilder();

        StringBuilder status3 = new StringBuilder();
        StringBuilder status4 = new StringBuilder();
        long count = meetingsByMeetingId.stream().filter(f -> f.getMeetingStatus() == 4).count();
        CountDownLatch countDownLatch = new CountDownLatch((int) count);
        try {
            for (MeetingUser meetingUser : meetingsByMeetingId) {
                //只查询外部人员反馈状态
                if (!Strings.isNullOrEmpty(meetingUser.getUserId())) {
                    continue;
                }
                String userName = meetingUser.getUserName();
                switch (meetingUser.getMeetingStatus()) {
                    case 1:
                    case 2:
                        status1.append(userName).append(SYMBOL);
                        break;
                    case 3:
                        status3.append(userName).append(SYMBOL);
                        break;
                    case 4:

                        ThreadUtil.execAsync(() -> {
                            try {
                                status4.append(userName).append("拒绝参会,原因:").append(messageService.qryMessageBySendIdAndReceiveId(userName, meeting.getOrgUserNumber())).append("\n");
                            } catch (Exception e) {
                                log.error(e.getMessage());
                            } finally {
                                countDownLatch.countDown();
                            }
                        });
                        break;
                }

            }
            countDownLatch.await(30, TimeUnit.SECONDS);
        } catch (Exception e) {
            log.error(e.getMessage());
        }
        if (!status1.toString().isEmpty()) {
            status1.append("尚未反馈通知\n");
        }
        if (!status3.toString().isEmpty()) {
            status1.append(status3.append("确定参会\n"));
        }
        if (!status4.toString().isEmpty()) {
            status1.append(status4);
        }
        return status1.toString();

    }

    @Override
    public Map<String, Object> getMeetingDataByYearAndWeek(String userNumber, String year, String week) {
        HashMap<String, Object> data = Maps.newHashMap();
        if (Strings.isNullOrEmpty(year)) {
            year = String.valueOf(DateUtil.thisYear());
        }
        if (Strings.isNullOrEmpty(week)) {
            week = String.valueOf(DateUtil.thisWeekOfYear());
        }
        Calendar calendar = CalendarUtil.calendar();
        calendar.set(Calendar.YEAR, Integer.parseInt(year));
        calendar.set(Calendar.WEEK_OF_YEAR, Integer.parseInt(week));

        Date startTime = CalendarUtil.beginOfWeek(calendar).getTime();
        Date endTime = DateUtil.offset(CalendarUtil.endOfWeek(calendar).getTime(), DateField.DAY_OF_WEEK, -2);

        List<PersonMeetingDTO> meetings = meetingMapper.getMeetingByMpid(userNumber, startTime, endTime);


        data.put("data", CollectionUtil.sort(meetings, Comparator.comparing(PersonMeetingDTO::getStartTime))
        );
        //上一周是哪年
        data.put("nextWeekYear", String.valueOf(DateUtil.year(DateUtil.offsetWeek(calendar.getTime(), 1))));
        data.put("preWeekYear", String.valueOf(DateUtil.year(DateUtil.offsetWeek(calendar.getTime(), -1))));
        data.put("year", year);
        data.put("week", week);
        //上一周是哪年的第多少周
        data.put("preWeek", String.valueOf(DateUtil.weekOfYear(DateUtil.offsetWeek(calendar.getTime(), -1))));
        data.put("nextWeek", String.valueOf(DateUtil.weekOfYear(DateUtil.offsetWeek(calendar.getTime(), 1))));
        data.put("startDate", DateUtil.format(startTime, DatePattern.NORM_DATE_PATTERN));
        data.put("endDate", DateUtil.format(endTime, DatePattern.NORM_DATE_PATTERN));

        return data;
    }

    @Override
    public Map<String, Object> getMeetingsByMonth(String userNumber, Date month) {

        HashMap<String, Object> result = Maps.newHashMap();
        DateTime start = DateUtil.beginOfMonth(month);
        DateTime end = DateUtil.endOfMonth(month);
        List<PersonMeetingDTO> meetings = meetingMapper.getMeetingByMpid(userNumber, start, end);
        result.put("data", CollectionUtil.sort(meetings, Comparator.comparing(PersonMeetingDTO::getStartTime)));
        DateTime next = DateUtil.offsetMonth(month, 1);
        DateTime pre = DateUtil.offsetMonth(month, -1);
        //从0开始计数
        result.put("preMonth", DateUtil.format(pre, DatePattern.NORM_MONTH_PATTERN));
        result.put("nextMonth", DateUtil.format(next, DatePattern.NORM_MONTH_PATTERN));
        result.put("curMonth", DateUtil.format(month, DatePattern.NORM_MONTH_PATTERN));
        return result;
    }


    @Override
    @CacheEvict(cacheNames = CaffeineConfig.MEETINGS, allEntries = true)
    public Integer updateMeeting(Meeting params) {
        Meeting meeting = meetingMapper.selectCzHrtableById(params.getId());
        if (!Objects.equals(params.getOrgUserNumber(), meeting.getOrgUserNumber())) {
            throw new BIZException("您无权修改会议");
        }
        if (Strings.isNullOrEmpty(params.getProjectId()) && Strings.isNullOrEmpty(params.getProjectName())) {
            throw new BIZException("新建会议必须关联项目");
        }
        params.setAmPm(DateUtil.isAM(params.getStartTime()) ? "1" : "2");
        //添加会议发起者
        MeetingUser mu = new MeetingUser();
        mu.setUserId(params.getOrgUserNumber());
        params.getParticipantList().add(mu);
        //会议先行 新建项目 关联到会议
        if (Strings.isNullOrEmpty(params.getProjectId()) && !Strings.isNullOrEmpty(params.getProjectName())) {
            CreateFolder createFolder = projectManageService.insertCreateFolder(params.getProjectName(), params.getOrgUserNumber());
            params.setProjectId(String.valueOf(createFolder.getId()));
            projectService.createProjectByProjectName(params.getProjectName(), params.getProjectId());
        }
        Boolean update = false;
        if (!(params.getStartTime().equals(meeting.getStartTime())
                && params.getEndTime().equals(meeting.getEndTime())
                && Objects.equals(params.getSite(), meeting.getSite())
                && Objects.equals(params.getType(), meeting.getType()))
        ) {
            update = true;
            //会议时间 地点发生变更
            if (Objects.equals(meeting.getType(), MeetingType.OFFLINE_INNER.getType()) || Objects.equals(meeting.getType(), MeetingType.OFFLINE_OUTER.getType())) {
                //线下会议室取消和重新预定
                cancelOaMeeting(meeting);
            }
            if (Objects.equals(params.getType(), MeetingType.ONLINE.getType())) {
                //线上新增会议
                String onlineRoomId = orderOnlineMeetingRoom(params);
                params.setSite(onlineRoomId);
            } else if (Objects.equals(params.getType(), MeetingType.OFFLINE_INNER.getType()) || Objects.equals(params.getType(), MeetingType.OFFLINE_OUTER.getType())) {
                orderOaMeeting(params);
            }
        }
        //参会人员对比
        List<MeetingUser> meetingsByMeetingId = meetingUserService.getMeetingsByMeetingId(params.getId());
        if (CollectionUtil.isEqualList(params.getParticipantList(), meetingsByMeetingId)) {
            if (update) {
                //发生变更通知
                sendMessage2User(meeting, params, meetingsByMeetingId, MessageType.MODIFY);
            }
        } else {
            //新人员在旧人员里修改 不在新增
            for (MeetingUser meetingUser : params.getParticipantList()) {
                MessageType messageType = meetingsByMeetingId.contains(meetingUser) ? MessageType.MODIFY : MessageType.NEW;
                sendMessage2User(meeting, params, com.google.common.collect.Lists.newArrayList(meetingUser), messageType);
            }
            //旧人员不在新人员 取消
            for (MeetingUser meetingUser : meetingsByMeetingId) {
                if (!params.getParticipantList().contains(meetingUser)) {
                    sendMessage2User(meeting, params, com.google.common.collect.Lists.newArrayList(meetingUser), MessageType.CANCEL);
                }
            }
        }
        int i = meetingMapper.updateByPrimaryKeySelective(params);
        meetingUserService.deleteByMeetingId(params.getId());
        for (MeetingUser meetingUser : params.getParticipantList()) {
            meetingUser.setMeetingId(params.getId());
            meetingUser.setMeetingStatus(1);
        }
        meetingUserService.batchInsert(params.getParticipantList());
        //重新生成会议通知单
        meetingFileService.delByMeetingIdAndFileType(params.getId(), MeetingFileType.MEETING_NOTIFY);
        genMeetingNotifyDoc(params);

        return i;
    }


    @Override
    @CacheEvict(cacheNames = CaffeineConfig.MEETINGS, allEntries = true)
    public Integer deleteMeeting(String id, String userNumber) {
        Meeting meeting = meetingMapper.selectByPrimaryKey(id);
        if (Objects.equals(meeting.getType(), MeetingType.OFFLINE_INNER.getType()) || Objects.equals(meeting.getType(), MeetingType.OFFLINE_OUTER.getType())) {
            cancelOaMeeting(meeting);
        }
        Integer integer = meetingMapper.deleteMeeting(id, userNumber);
        if (integer == 1) {
            List<MeetingUser> meetingsByMeetingId = meetingUserService.getMeetingsByMeetingId(meeting.getId());
            sendMessage2User(meeting, null, meetingsByMeetingId, MessageType.CANCEL);
        }
        return integer;
    }

    @Override
    @CacheEvict(cacheNames = CaffeineConfig.MEETINGS, allEntries = true)
    public Integer deleteMeetingIds(String[] ids, String userNumber) {
//        Integer integer = meetingMapper.deleteMeetingIds(ids, userNumber);
//        if (integer == ids.length) {
//            for (String id : ids) {
//                notifyUserByMeetingId(id, userNumber);
//            }
//        }
//        return integer;
        return null;
    }


    /**
     * 发送消息给用户
     *
     * @param meeting
     * @param newMeeting
     * @param meetingUsers
     * @param messageType
     */
    private void sendMessage2User(Meeting meeting, Meeting newMeeting, List<MeetingUser> meetingUsers, MessageType messageType) {
        for (MeetingUser meetingUser : meetingUsers) {

            Message message = new Message();
            message.setSendUserId(meeting.getOrgUserNumber());
            if (Strings.isNullOrEmpty(meetingUser.getUserId())) {
                message.setRecvUserId(meetingUser.getUserName());
            } else {
                message.setRecvUserId(meetingUser.getUserId());
            }

            message.setAssociatedId(meeting.getId());
            //id为空 是外部人员 通过微信平台发消息
            String messageTemplate = "";
            switch (messageType) {
                case CANCEL:
                    messageTemplate =
                            String.format("会议取消：您于%s有%s：%s已取消。", DateUtil.format(meeting.getStartTime(), "yyyy年MM月dd日HH时mm分"), MeetingType.getDesc(meeting.getType()), meeting.getMeetingHeadline());

                    break;
                case NEW:
                    messageTemplate = String.format("您于%s有%s：%s,请记得准时参会", DateUtil.format(meeting.getStartTime(), "yyyy年MM月dd日HH时mm分"), MeetingType.getDesc(meeting.getType()), meeting.getMeetingHeadline());
                    break;
                case MODIFY:
                    messageTemplate = String.format("会议变更：您于%s有%s：%s，变更为  %s：%s", DateUtil.format(meeting.getStartTime(), "yyyy年MM月dd日HH时mm分"), MeetingType.getDesc(meeting.getType()), meeting.getMeetingHeadline(), DateUtil.format(newMeeting.getStartTime(), "yyyy年MM月dd日HH时mm分") + MeetingType.getDesc(newMeeting.getType()), newMeeting.getMeetingHeadline());
                    break;
            }
            message.setContent(messageTemplate);
            ThreadUtil.execAsync(() -> messagePush.pushMessage(message));
        }

    }

    @Override
    public Map<String, List<OAMeetingInfo>> getOfflineMeetings(String date) {
        Map<String, List<OAMeetingInfo>> oaMeetingInfos = null;
        HashMap<String, Object> params = Maps.newHashMap();
        params.put("date",
                date);

        String post = HttpUtil.post(meetingRoomList, params);
        Map map = JSONObject.parseObject(post, Map.class);
        String status = String.valueOf(map.get("status"));
        String data = String.valueOf(map.get("data"));
        if ("200".equalsIgnoreCase(status)) {
            if (!Strings.isNullOrEmpty(data)) {
                oaMeetingInfos = JSON.parseObject(data, Map.class);
            }
        } else {
            throw new BIZException(String.valueOf(map.get("message")));
        }
        //含有集团和19楼的是外部会议室


        return oaMeetingInfos;
    }


    @Override
    public String weChatMessagePush(String message) {
        HashMap<String, Object> params = Maps.newHashMap();
        Date date = DateUtil.nextWeek();
        params.put("phone", "");
        params.put("project_no", "");
        params.put("project_name", "");
        params.put("project_status", "");
        params.put("apply_name", "");
        params.put("apply_time", DateUtil.format(date, DatePattern.NORM_DATETIME_MINUTE_PATTERN));
        params.put("plot", "");
        params.put("grade", "");
        params.put("url", "");
        params.put("timestamp", System.currentTimeMillis());
        params.put("nonce_str", RandomUtil.randomString(16));
        weChatMessagePush.pushMessage(message, params);
        return "";
    }

    @Override
    public void uploadMeetingVideo(UploadVideoVO uploadVideoVO) {
        File file = uploadVideoVO.getFile();

        String suffix = FileUtil.extName(file);

        //视频 语音 语音转文字 用的id一样 后缀名不同
        String s = IdUtil.objectId();
        String path = s + "." + suffix;
        try (FileInputStream inputStream = new FileInputStream(file)) {
            minIoUtil.putObject(MinIoUtil.MEETING_BUCKET, path, inputStream);
            MeetingFile meetingFile = new MeetingFile();
            meetingFile.setId(IdUtil.objectId());
            meetingFile.setMeetingId(uploadVideoVO.getMeetingId());
            meetingFile.setFileName(file.getName());
            meetingFile.setFileExt(suffix);
            meetingFile.setFilePath(path);
            meetingFile.setFileType(MeetingFileType.MEETING_FILES.getCode());
            meetingFile.setUserNumber(uploadVideoVO.getUserNumber());
            meetingFileService.uploadFile(meetingFile);

            if ("wmv".equals(suffix)) {
                COMMON_NAME.set(s);
                video2Voice(uploadVideoVO, file);
            }
        } catch (Exception e) {
            throw new BIZException(e);
        } finally {
            COMMON_NAME.remove();
        }

        FileUtil.del(file);

    }

    /**
     * 视频转文字
     *
     * @param uploadVideoVO
     * @param file
     * @throws IOException
     */
    private void video2Voice(UploadVideoVO uploadVideoVO, File file) throws IOException {
        MeetingFile meetingFile;
        String suffix;
        String path;
        String absolutePath = file.getAbsolutePath();
        String replace = absolutePath.replace("wmv", "wav");
        if (FileUtil.exist(replace)) {
            FileUtil.del(replace);
        }
        Process exec = RuntimeUtil.exec(String.format("%s -i %s -f wav -ar 16000 %s", ffmpegPath, absolutePath, replace));
        String result = RuntimeUtil.getResult(exec);
        log.info(result);

        File file1 = FileUtil.file(replace);
        suffix = FileUtil.extName(file1);
        path = COMMON_NAME.get() + "." + suffix;
        String meetingId = uploadVideoVO.getMeetingId();
        try (FileInputStream inputStream1 = new FileInputStream(file1)) {
            minIoUtil.putObject(MinIoUtil.MEETING_BUCKET, path, inputStream1);
            meetingFile = new MeetingFile();
            meetingFile.setId(IdUtil.objectId());
            meetingFile.setMeetingId(meetingId);
            meetingFile.setFileName(file1.getName());
            meetingFile.setFileExt(suffix);
            meetingFile.setFilePath(path);
            meetingFile.setFileType(MeetingFileType.MEETING_VOICE.getCode());
            meetingFile.setUserNumber(uploadVideoVO.getUserNumber());
            meetingFileService.uploadFile(meetingFile);
            voice2Text(replace, uploadVideoVO);
        }
        FileUtil.del(replace);

    }

    //语音转文字
    private void voice2Text(String replace, UploadVideoVO uploadVideoVO) throws IOException {
        MeetingFile meetingFile;
        String path;
//        List<String> texts = UtserviceUtil.wav2Text(replace);
        List<String> texts = Arrays.asList(new String[]{"语音转译测试数据1", "语音转译测试数据2", "语音转译测试数据3"});

        String meetingId = uploadVideoVO.getMeetingId();

        String fileName = meetingId + "-会议纪要.txt";

        InputStreamSource inputStreamSource = new ByteArrayResource(Joiner.on('\n').join(texts).getBytes(StandardCharsets.UTF_8));
        String extName = "txt";
        meetingFile = new MeetingFile();
        meetingFile.setId(IdUtil.objectId());
        path = COMMON_NAME.get() + "." + extName;
        minIoUtil.putObject(MinIoUtil.MEETING_BUCKET, path, inputStreamSource.getInputStream());
        meetingFile.setFileType(MeetingFileType.MEETING_TALK_TEXT.getCode());
        meetingFile.setFilePath(path);
        meetingFile.setMeetingId(meetingId);
        meetingFile.setUserNumber(uploadVideoVO.getUserNumber());
        meetingFile.setFileExt(extName);
        meetingFile.setFileName(fileName);

        meetingFileService.uploadFile(meetingFile);
    }

    @Override
//    @Cacheable(keyGenerator = "myKeyGenerator", cacheNames = CaffeineConfig.MEETINGS, sync = true)
    public List<MeetingListDTO> list(MeetingsReq meetingsReq) {
        if (meetingsReq.getStartTime() == null && meetingsReq.getEndTime() == null) {
            Date now = new Date();
            meetingsReq.setStartTime(DateUtil.beginOfDay(DateUtil.offsetDay(now, -15)));
            meetingsReq.setEndTime(DateUtil.endOfDay(DateUtil.offsetDay(now, 15)));
        }
        meetingsReq.setStartTime(DateUtil.beginOfDay(meetingsReq.getStartTime()));
        meetingsReq.setEndTime(DateUtil.endOfDay(meetingsReq.getEndTime()));
        MeetingExample meetingExample = new MeetingExample();
        MeetingExample.Criteria criteria = meetingExample.createCriteria();
        //状态正常
        criteria.andStatusEqualTo(1);
        if (meetingsReq.getStartTime() != null) {
            criteria.andStartTimeGreaterThanOrEqualTo(meetingsReq.getStartTime());
        }

        if (meetingsReq.getEndTime() != null) {
            criteria.andStartTimeLessThanOrEqualTo(meetingsReq.getEndTime());
        }

        if (!Strings.isNullOrEmpty(meetingsReq.getMeetingName())) {
            criteria.andMeetingHeadlineLike("%" + meetingsReq.getMeetingName() + "%");
        }

        //参与会议
        if ("2".equalsIgnoreCase(meetingsReq.getMeetingType())) {
            List<MeetingUser> meetingsByUserNumber = meetingUserService.getMeetingsByUserNumber(meetingsReq.getUserNumber());
            List<String> ids = meetingsByUserNumber.stream().map(MeetingUser::getMeetingId).collect(Collectors.toList());
            if (CollectionUtil.isNotEmpty(ids)) {
                criteria.andIdIn(ids);
            }
        }

        meetingExample.setOrderByClause("start_time DESC");

        List<Meeting> meetings = meetingMapper.selectByExample(meetingExample);
        List<MeetingListDTO> data = Lists.newArrayListWithCapacity(meetings.size());

        for (Meeting meeting : meetings) {
            //线上
//            if (Objects.equals(meeting.getType(), MeetingType.ONLINE.getType())) {
//                try {
//                    String roominfoList = webserviceClient.getRoominfoList(meeting.getSite());
//                    Document document = XmlUtil.parseXml(roominfoList);
//                    String endTime = String.valueOf(XmlUtil.getByXPath("//room/weekendtime", document, XPathConstants.STRING));
//                    String startTime = String.valueOf(XmlUtil.getByXPath("//room/weekstarttime", document, XPathConstants.STRING));
//
//                } catch (Exception e) {
//                    e.printStackTrace();
//                }
//            }
            //已召开 1 未召开 2

            boolean i = meeting.getStartTime().before(new Date());

            if (Objects.equals(meetingsReq.getMeetingDone(), "1")) {
                if (i) {
                    data.add(getDTO(meeting)
                    );
                }
            } else {
                if (!i) {
                    data.add(getDTO(meeting)
                    );
                }
            }
        }

        for (int i = 0; i < data.size(); i++) {
            data.get(i).setIndex(i + 1);
        }
        return data;
    }

    @Override
    public HashMap<String, Object> qryDetailByMeetingId(String meetingId) {
        HashMap<String, Object> result = Maps.newHashMap();
        CountDownLatch countDownLatch = new CountDownLatch(3);
        ThreadUtil.execAsync(() -> {
            try {
                Meeting meeting = meetingMapper.selectByPrimaryKey(meetingId);
                MeetingListDTO dto = getDTO(meeting);
                result.put("detail", dto);
            } finally {
                countDownLatch.countDown();
            }
        });
        ThreadUtil.execAsync(() -> {
            try {
                List<MeetingVoiceDTO> voices = meetingFileService.getVoices(meetingId);

                result.put("voices", voices);
            } finally {
                countDownLatch.countDown();
            }
        });
        ThreadUtil.execAsync(() -> {
            try {
                List<MeetingFile> meetingFiles = meetingFileService.qryByMeetingId(meetingId);

                result.put("files", meetingFiles);
            } finally {
                countDownLatch.countDown();
            }
        });

        try {
            countDownLatch.await(30, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            throw new BIZException("查询失败");
        }

        return result;
    }

    /**
     * 会议列表子项详情
     *
     * @param meeting
     * @return
     */
    private MeetingListDTO getDTO(Meeting meeting) {
        CountDownLatch countDownLatch = new CountDownLatch(4);
        String duration = DateUtil.formatBetween(meeting.getStartTime(), meeting.getEndTime());
        MeetingListDTO.MeetingListDTOBuilder meetingListDTOBuilder = MeetingListDTO.builder()
                .meetingId(meeting.getId())
                .meetingName(meeting.getMeetingHeadline())
                .startTime(meeting.getStartTime())
                .duration(duration)
                .remark(meeting.getRemarks())
                .endTime(meeting.getEndTime())
                .roomId(meeting.getSite())
                .type(meeting.getType());

        try {
            ThreadUtil.execAsync(() -> {
                try {
                    String projectName = projectService.qryByProjectId(meeting.getProjectId()).getProjectName();
                    meetingListDTOBuilder.projectName(projectName);
                } catch (Exception e) {
                    log.error(e.getMessage(), e);
                } finally {
                    countDownLatch.countDown();
                }
            });
            ThreadUtil.execAsync(() -> {
                try {
                    String units = meetingUserService.getMeetingsByMeetingId(meeting.getId()).stream().filter(f -> f.getUserId().isEmpty()).map(MeetingUser::getUserName).collect(Collectors.joining(SYMBOL));
                    meetingListDTOBuilder.meetingUnits(units);
                } catch (Exception e) {
                    log.error(e.getMessage(), e);
                } finally {
                    countDownLatch.countDown();
                }
            });
            ThreadUtil.execAsync(() -> {
                try {
                    meetingListDTOBuilder.ackMsg(qryAttendMeetingUsersStatus(meeting));
                } catch (Exception e) {
                    log.error(e.getMessage(), e);
                } finally {
                    countDownLatch.countDown();
                }
            });
            ThreadUtil.execAsync(() -> {
                try {
                    String originator = userService.qryNameByUserNumber(meeting.getOrgUserNumber());
                    meetingListDTOBuilder.meetingOriginator(originator);
                } catch (Exception e) {
                    log.error(e.getMessage(), e);
                } finally {
                    countDownLatch.countDown();
                }
            });

            countDownLatch.await(30, TimeUnit.SECONDS);
        } catch (
                Exception e) {
            throw new BIZException(e);
        }

        return meetingListDTOBuilder.build();

    }

    @Override
    @CacheEvict(cacheNames = CaffeineConfig.MEETINGS, allEntries = true)
    public String addMeeting(Meeting met) {
        if (Strings.isNullOrEmpty(met.getProjectId()) && Strings.isNullOrEmpty(met.getProjectName())) {
            throw new BIZException("新建会议必须关联项目");
        }
        //编码特殊字符
        if (Objects.equals(met.getType(), MeetingType.ONLINE.getType())) {
            //线上会议室
            String roomId = orderOnlineMeetingRoom(met);
            met.setSite(roomId);
        }
        //会议先行 新建项目 关联到会议
        if (Strings.isNullOrEmpty(met.getProjectId()) && !Strings.isNullOrEmpty(met.getProjectName())) {
            CreateFolder createFolder = projectManageService.insertCreateFolder(met.getProjectName(), met.getOrgUserNumber());
            met.setProjectId(String.valueOf(createFolder.getId()));
            projectService.createProjectByProjectName(met.getProjectName(), met.getProjectId());
        }
        String meetingId = IdUtil.objectId();
        met.setId(meetingId);
        met.setAmPm(DateUtil.isAM(met.getStartTime()) ? "1" : "2");
        //线下会议向OA系统预定会议室
        if (Objects.equals(met.getType(), MeetingType.OFFLINE_INNER.getType()) || Objects.equals(met.getType(), MeetingType.OFFLINE_OUTER.getType())) {
            orderOaMeeting(met);
        }
        Integer integer = meetingMapper.insertSelective(met);

        List<MeetingUser> participants = met.getParticipantList();
        //添加会议发起者
        MeetingUser meetingUser = new MeetingUser();
        meetingUser.setUserId(met.getOrgUserNumber());

        participants.add(meetingUser);


        participants.forEach((f) -> {
            f.setMeetingId(meetingId);
            f.setMeetingStatus(1);
        });
        meetingUserService.batchInsert(participants);

        //会议通知单
        genMeetingNotifyDoc(met);
        //通知与会人员
        ThreadUtil.execAsync(() -> notifyUser(met));
        return meetingId;
    }

    /**
     * 预定线上会议室
     *
     * @param met
     * @return
     */
    private String orderOnlineMeetingRoom(Meeting met) {
        met.setMeetingHeadline(new Html4Unescape().replace(met.getMeetingHeadline()).toString());

        String roomPwd = "1234";
        String roomId = webserviceClient.createMeeting(met, roomPwd);
        return roomId;
    }

    /**
     * 向oa预定会议室
     *
     * @param met
     */
    private void orderOaMeeting(Meeting met) {

        HashMap<String, Object> params = Maps.newHashMap();
        params.put("title", met.getMeetingHeadline());
        params.put("content", met.getContent());
        params.put("date", DateUtil.format(met.getStartTime(), DatePattern.NORM_DATE_PATTERN));
        params.put("startTime", DateUtil.format(met.getStartTime(), DATE_FORMAT_HH_MM));
        params.put("endTime", DateUtil.format(met.getEndTime(), DATE_FORMAT_HH_MM));
        params.put("host", met.getOrgUserNumber());
        params.put("inPersons", met.getParticipantList().stream().filter(f -> !Strings.isNullOrEmpty(f.getUserId())).map(MeetingUser::getUserId).collect(Collectors.joining(",")));
        params.put("outPersons", met.getParticipantList().stream().filter(f -> Strings.isNullOrEmpty(f.getUserId())).map(MeetingUser::getUserName).collect(Collectors.joining(",")));
        params.put("meetingRoom", met.getSite());

        String post = HttpUtil.post(meetingRoomOrder, params);
        JSONObject jsonObject = JSON.parseObject(post);
        if (!Objects.equals(jsonObject.get("status"), "200")) {
            throw new BIZException(String.valueOf(jsonObject.get("message")));
        }
    }

    /**
     * 向oa取消预定会议室
     *
     * @param met
     */
    private void cancelOaMeeting(Meeting met) {

        HashMap<String, Object> params = Maps.newHashMap();
        params.put("date", DateUtil.format(met.getStartTime(), DatePattern.NORM_DATE_PATTERN));
        params.put("startTime", DateUtil.format(met.getStartTime(), DATE_FORMAT_HH_MM));
        params.put("endTime", DateUtil.format(met.getEndTime(), DATE_FORMAT_HH_MM));
        params.put("meetingRoom", met.getSite());
        params.put("cancelUserNo", met.getOrgUserNumber());

        String post = HttpUtil.post(meetingRoomCancel, params);
        JSONObject jsonObject = JSON.parseObject(post);
        if (!Objects.equals(jsonObject.get("status"), "200")) {
            throw new BIZException(String.valueOf(jsonObject.get("message")));
        }

    }

    @Override
    @CacheEvict(cacheNames = CaffeineConfig.MEETINGS, allEntries = true)
    public void refreshMeetingList() {
        log.info("clear cache " + CaffeineConfig.MEETINGS);
    }

    @Override
    public List<Meeting> todayMeetings(String userNumber) {
        List<MeetingUser> meetings = meetingUserService.getMeetingsByUserNumber(userNumber);
        if (CollectionUtil.isEmpty(meetings)) {
            return null;
        }
        List<String> ids = meetings.stream().map(MeetingUser::getMeetingId).collect(Collectors.toList());
        MeetingExample meetingExample = new MeetingExample();
        Date today = new Date();
        meetingExample.createCriteria().andStartTimeBetween(DateUtil.beginOfDay(today), DateUtil.endOfDay(today)).andIdIn(ids);
        meetingExample.setOrderByClause("start_time desc");
        List<Meeting> list = meetingMapper.selectByExample(meetingExample);
        return list;
    }


    @Override
    public List<OuterSite> offlineOuterSites(String key) {
        MeetingExample meetingExample = new MeetingExample();
        meetingExample.createCriteria().andTypeEqualTo(MeetingType.OFFLINE_OUTER_NEW.getType()).andSiteLike("%" + key + "%");
        List<Meeting> meetings = meetingMapper.selectByExample(meetingExample);
        List<OuterSite> sites = Lists.newArrayList();
        for (Meeting meeting : meetings) {
            sites.add(new OuterSite(meeting.getSite(), ""));
        }

        if (CollectionUtil.isEmpty(sites)) {
            sites.add(new OuterSite(key, "此地点不存在,点击进行新建"));
        }
        return sites;
    }

    @Override
    public Meeting qryOne(String meetingId) {
        return meetingMapper.selectByPrimaryKey(meetingId);
    }

    @Override
    public List<Meeting> qryMeetingIdByProjectId(String projectId) {

        MeetingExample meetingExample = new MeetingExample();
        meetingExample.createCriteria().andProjectIdEqualTo(projectId);
        List<Meeting> meetings = meetingMapper.selectByExample(meetingExample);
        return meetings;
    }

    @Override
    public OpenMeeting openMeeting(String userNumber, String meetingId) {
        Meeting meeting = meetingMapper.selectByPrimaryKey(meetingId);
        OpenMeeting openMeeting1 = meetingInviteLink.genInviteLink(meeting, userNumber);
        return openMeeting1;
    }


    /**
     * 生成会议通知单
     *
     * @param met
     */
    @SneakyThrows
    private void genMeetingNotifyDoc(Meeting met) {
        File fileXlsxTemp = com.seari.custody.util.FileUtil.copyResourceFileToTemp("patternplate/meeting/meeting_notify_template.docx");

        try (XWPFDocument xwpfDocument = DocUtil.create(fileXlsxTemp); ByteArrayOutputStream arrayOutputStream = new ByteArrayOutputStream(); BufferedOutputStream output = new BufferedOutputStream(arrayOutputStream);
        ) {
            List<XWPFTable> tables = xwpfDocument.getTables();
            XWPFTable xwpfTable = tables.get(0);
            String startTime = DateUtil.format(met.getStartTime(), DatePattern.CHINESE_DATE_PATTERN) + "  " + DateUtil.format(met.getStartTime(), DATE_FORMAT_HH_MM) + "-" + DateUtil.format(met.getEndTime(), DATE_FORMAT_HH_MM);
            String curTime = DateUtil.format(DateUtil.date(), DatePattern.CHINESE_DATE_PATTERN);

            String site = met.getSite();

            String owner = "监护管理办公室";


            StringBuilder participants = new StringBuilder(owner);
            for (MeetingUser meetingUser : met.getParticipantList()) {
                if (StrUtil.isBlank(meetingUser.getUserId()) && StrUtil.isNotBlank(meetingUser.getUserName())) {
                    participants.append("、").append(meetingUser.getUserName());
                }
            }
            String[] params = new String[]{met.getMeetingHeadline(), startTime, MeetingType.getDesc(met.getType()) + ":" + site, met.getContent().trim(), owner, participants.toString(), met.getRemarks()};
            for (int i = 0; i < 7; i++) {
                xwpfTable.getRow(i).getCell(1).setParagraph(OutlineProjectCalc.createParagraphCenter(params[i]));
            }

            xwpfDocument.setParagraph(createParagraph(curTime, false, true, true, 14, "黑体"), 3);
            xwpfDocument.write(output);
            output.flush();
            InputStreamSource inputStreamSource = new ByteArrayResource(arrayOutputStream.toByteArray());

            String suffix = "docx";
            String path = IdUtil.objectId() + "." + suffix;

            minIoUtil.putObject
                    (MinIoUtil.MEETING_BUCKET, path, inputStreamSource.getInputStream());
            MeetingFile meetingFile = new MeetingFile();
            meetingFile.setId(IdUtil.objectId());
            meetingFile.setMeetingId(met.getId());
            meetingFile.setFileName(met.getMeetingHeadline() + "_会议通知单" + "." + suffix);
            meetingFile.setFileExt(suffix);
            meetingFile.setFilePath(path);
            meetingFile.setUserNumber(met.getOrgUserNumber());
            meetingFile.setFileType(MeetingFileType.MEETING_NOTIFY.getCode());
            meetingFileService.uploadFile(meetingFile);

        } catch (Exception e) {
            throw new BIZException("生成会议通知单失败", e);

        } finally {
            FileUtil.del(fileXlsxTemp);
        }
    }

    /**
     * 给与会人员发消息
     *
     * @param met
     */
    private void notifyUser(Meeting met) {

        for (MeetingUser meetingUser : met.getParticipantList()) {
            String messageTemplate = String.format("您于%s有%s：%s", DateUtil.format(met.getStartTime(), "yyyy年MM月dd日HH时mm分"), MeetingType.getDesc(met.getType()), met.getMeetingHeadline());

            Message message = new Message();
            message.setSendUserId(met.getOrgUserNumber());
            message.setAssociatedId(met.getId());
            String receiveId = "";
            //id为空 是外部人员 通过微信平台发消息
            if (Strings.isNullOrEmpty(meetingUser.getUserId())) {
                receiveId = meetingUser.getUserName();
                messageTemplate += ",是否参会?";
            } else {
                receiveId = meetingUser.getUserId();
                messageTemplate += ",请记得准时参会!";
            }
            message.setRecvUserId(receiveId);
            message.setContent(messageTemplate);
            messagePush.pushMessage(message);
        }


    }


    @SneakyThrows
    public static void main(String[] args) {
//
//        Date date = new Date();
//        DateTime dateTime = DateUtil.offsetMonth(date, 1);
//        System.out.println(DateUtil.month(date));
//        String path = ResourceUtil.getResource("tools/ffmpeg.exe").getPath();
//        String format = String.format("ffmpeg -i %s -f wav -ar 16000 %s", "D:\\a\\custody\\upload\\615131458cef6ed6c0bcb312.wmv", "D:\\a\\custody\\upload\\615131458cef6ed6c0bcb312.wav");
//        Process exec = RuntimeUtil.exec(format);
//        System.out.println("111");
//        String result = RuntimeUtil.getResult(exec);
//        System.out.println("ddd");
//
//
//        System.out.println(result);

        MeetingServiceImpl meetingService = new MeetingServiceImpl();
        meetingService.genMeetingNotifyDoc(null);
    }


}
